package com.yxx.aspect;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson2.JSON;
import com.yxx.annotation.OptLogger;
import com.yxx.entity.OperationLog;
import com.yxx.manager.AsyncManager;
import com.yxx.manager.factory.AsyncFactory;
import com.yxx.utils.IpUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;

/**
 * AOP记录操作日志
 * @author yuxiangxun
 * @date 2025/1/29
 * @apiNote
 */
@Aspect
@Component
public class OptLogAspect {
    /**
     * 请求开始时间
     */
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * 设置操作日志切入点，记录操作日志，在注解的位置切入代码
     */
    @Pointcut("@annotation(com.yxx.annotation.OptLogger)")
    private void optLogPointCut(){
    }

    /**
     * 前置通知，在方法执行前执行
     */
    @Before("optLogPointCut()")
    private void before(){
        // 记录请求开始时间
        startTime.set(System.currentTimeMillis());
    }

    /**
     * 后置返回通知，在方法返回结果后执行
     * 连接点正常返回通知，拦截用户操作日志，正常执行完成后执行，如果连接点抛出异常，则不会执行
     * @param joinPoint 切面方法的信息
     * @param result 返回结果
     */
    @AfterReturning(value = "optLogPointCut()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result){
        // 从切面织入点通过反射机制获取织入点处的方法 通过joinPoint获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取切入点的方法 通过方法签名获取方法
        Method method = signature.getMethod();

        // 获取操作
        // 获取 API 注解 -> Swagger 注解 -> 操作  从声明类中获取API注解
        Api api = (Api) signature.getDeclaringType().getAnnotation(Api.class);
        // 获取 ApiOperation 注解
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        // 获取 OptLogger 注解
        OptLogger optLogger = method.getAnnotation(OptLogger.class);

        // 获取request
        // 通过 当前请求的上下文信息 获取 当前请求的 RequestAttributes 对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // 根据 ServletRequestAttributes 获取当前请求的 request 对象
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

        // 获取到的日志保存到数据库  -> 新建OperationLog()对象，封装数据后进行插入数据库操作
        OperationLog operationLog = new OperationLog();
        // 操作模块
        operationLog.setModule(api.tags()[0]);
        // 操作类型
        operationLog.setType(optLogger.value());
        // 请求URI
        operationLog.setUri(request.getRequestURI());

        // 获取请求的类名 通过目标对象得到类名
        String className = joinPoint.getTarget().getClass().getName();
        // 获取请求的方法名
        String methodName = method.getName();
        methodName = className + "." + methodName;
        // 请求方法名称  需要类名.方法名拼接
        operationLog.setName(methodName);

        // 操作描述
        operationLog.setDescription(apiOperation.value());

        // 请求参数
        if(joinPoint.getArgs()[0] instanceof MultipartFile){
            // 第一个参数是 MultipartFile 类型，只获取上传文件的原始文件名，并将其设置为操作日志的参数
            operationLog.setParams(((MultipartFile)joinPoint.getArgs()[0]).getOriginalFilename());
        }else{
            // 整个参数列表转换为 JSON 字符串，并设置为操作日志的参数
            operationLog.setParams(JSON.toJSONString(joinPoint.getArgs()));
        }

        // 请求方法
        operationLog.setMethod(Objects.requireNonNull(request.getMethod()));
        // 返回数据
        operationLog.setData(JSON.toJSONString(result));
        // 请求用户ID
        operationLog.setUserId(StpUtil.getLoginIdAsInt());
        // 请求用户昵称
        operationLog.setNickname("余相勋");

        // 操作ip和操作地址  胡图工具包中的ServletUtil.getClientIP(request)
        operationLog.setIpAddress(ServletUtil.getClientIP(request));
        operationLog.setIpSource(IpUtils.getIpSource(ServletUtil.getClientIP(request)));

        // 执行耗时
        operationLog.setTimes(System.currentTimeMillis() - startTime.get());
        // 这里应该将该线程移除
        startTime.remove();
        // 保存到数据库
        AsyncManager.getInstance().execute(AsyncFactory.recordOperation(operationLog));
    }
}
